package cas.ihep.util;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;

public class MemoryBuffer implements Closeable{

    private MappedByteBuffer buffer;
    private FileChannel mappedChannel;
    private Method cleanup;

    private static final Method sun_cleaner;
    private static final Class<?> sun_filechannel;
    static{
        Class<?> sun_filechannel1;
        Method sun_cleaner1;
        try {
            sun_filechannel1 =Class.forName("sun.nio.ch.FileChannelImpl");
            sun_cleaner1= sun_filechannel1.getDeclaredMethod("unmap", MappedByteBuffer.class);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            sun_filechannel1 =null;
            sun_cleaner1=null;
        }
        sun_filechannel = sun_filechannel1;
        sun_cleaner=sun_cleaner1;
    }

    private void init(File file,long pos,long size)throws IOException{
        mappedChannel=FileChannel.open(file.toPath(), StandardOpenOption.READ,StandardOpenOption.WRITE);
        long tmpSize=mappedChannel.size();
        if(pos<0){
            pos=mappedChannel.position();
        }
        if(size<0){
            size=tmpSize;
        }
        if(pos+size>tmpSize){
            mappedChannel.truncate(pos+size);
        }
        buffer=mappedChannel.map(FileChannel.MapMode.READ_WRITE,pos,size);
        if(sun_filechannel!=null && sun_filechannel.isInstance(mappedChannel) && sun_cleaner!=null){
            cleanup=sun_cleaner;
        }else {
            try {
                cleanup = mappedChannel.getClass().getDeclaredMethod("unmap", MappedByteBuffer.class);
                cleanup.setAccessible(true);
            } catch (NoSuchMethodException ignored) {
                cleanup = null;
            }
        }
    }

    public MemoryBuffer(File file) throws IOException {
        init(file,-1,-1);
    }

    public MemoryBuffer(File file,long pos, long size)throws IOException{
        init(file,pos,size);
    }

    public int capacity(){
        return buffer.capacity();
    }

    @Override
    public void close() throws IOException {
        try{
            if(cleanup!=null){
                cleanup.invoke(null,buffer);
            }
        } catch (IllegalAccessException | InvocationTargetException ignored) {
        }finally {
            mappedChannel.close();
        }
    }

    public int position(){
        return buffer.position();
    }

    public MemoryBuffer position(int pos){
        buffer.position(pos);
        return this;
    }

    public MemoryBuffer flip(){
        buffer.flip();
        return this;
    }

    public MemoryBuffer get(byte[] dst){
        buffer.get(dst);
        return this;
    }

    public byte get(){
        return buffer.get();
    }

    public MemoryBuffer get(byte[] dst,int off,int len){
        buffer.get(dst,off,len);
        return this;
    }

    public byte get(int i){
        return buffer.get(i);
    }

    public char getChar(){
        return buffer.getChar();
    }

    public char getChar(int i){
        return buffer.getChar(i);
    }

    public double getDouble(){
        return buffer.getDouble();
    }

    public double getDouble(int i){
        return buffer.getDouble(i);
    }

    public float getFloat(){
        return buffer.getFloat();
    }

    public float getFloat(int i){
        return buffer.getFloat(i);
    }

    public int getInt(){
        return buffer.getInt();
    }

    public int getInt(int i){
        return buffer.getInt(i);
    }

    public long getLong(){
        return buffer.getLong();
    }

    public long getLong(int i){
        return buffer.getLong(i);
    }

    public short getShort(){
        return buffer.getShort();
    }

    public short getShort(int i){
        return buffer.getShort(i);
    }

    public MemoryBuffer put(byte b){
        buffer.put(b);
        return this;
    }

    public MemoryBuffer put(byte[] src){
        buffer.put(src);
        return this;
    }

    public MemoryBuffer put(byte src[],int off,int len){
        buffer.put(src,off,len);
        return this;
    }

    public MemoryBuffer put(ByteBuffer src){
        buffer.put(src);
        return this;
    }

    public MemoryBuffer put(int i,byte b){
        buffer.put(i,b);
        return this;
    }

    public MemoryBuffer putChar(char ch){
        buffer.putChar(ch);
        return this;
    }

    public MemoryBuffer putChar(int i,char ch){
        buffer.putChar(i,ch);
        return this;
    }

    public MemoryBuffer putDouble(int i,double d){
        buffer.putDouble(i,d);
        return this;
    }

    public MemoryBuffer putDouble(double d){
        buffer.putDouble(d);
        return this;
    }

    public MemoryBuffer putFloat(float f){
        buffer.putFloat(f);
        return this;
    }

    public MemoryBuffer putFloat(int i,float f){
        buffer.putFloat(i,f);
        return this;
    }

    public MemoryBuffer putInt(int i){
        buffer.putInt(i);
        return this;
    }

    public MemoryBuffer putInt(int i,int v){
        buffer.putInt(i,v);
        return this;
    }

    public MemoryBuffer putLong(long l){
        buffer.putLong(l);
        return this;
    }

    public MemoryBuffer putLong(int i,long l){
        buffer.putLong(i,l);
        return this;
    }

    public MemoryBuffer putShort(short s){
        buffer.putShort(s);
        return this;
    }

    public MemoryBuffer putShort(int i,short s){
        buffer.putShort(i,s);
        return this;
    }

    public boolean isLoaded(){
        return buffer.isLoaded();
    }

    public boolean isDirect(){
        return buffer.isDirect();
    }

    public boolean isReadOnly(){
        return buffer.isReadOnly();
    }
}
